Completed
Pull Request — develop (#85)
by Xaver
01:09
created

map.js ➔ ... ➔ map.zoom   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 5
rs 9.4285
nop 0
1
define(['map/clientlayer', 'map/labellayer', 'leaflet', 'moment', 'locationmarker', 'rbush', 'helper'],
2
  function (ClientLayer, LabelLayer, L, moment, LocationMarker, rbush, helper) {
3
    'use strict';
4
5
    var options = {
6
      worldCopyJump: true,
7
      zoomControl: true
8
    };
9
10
    var ButtonBase = L.Control.extend({
11
      options: {
12
        position: 'bottomright'
13
      },
14
15
      active: false,
16
      button: undefined,
17
18
      initialize: function (f, o) {
19
        L.Util.setOptions(this, o);
20
        this.f = f;
21
      },
22
23
      update: function () {
24
        this.button.classList.toggle('active', this.active);
25
      },
26
27
      set: function (v) {
28
        this.active = v;
29
        this.update();
30
      }
31
    });
32
33
    var LocateButton = ButtonBase.extend({
34
      onAdd: function () {
35
        var button = L.DomUtil.create('button', 'ion-locate shadow');
36
        button.setAttribute('data-tooltip', _.t('button.tracking'));
37
        L.DomEvent.disableClickPropagation(button);
38
        L.DomEvent.addListener(button, 'click', this.onClick, this);
39
40
        this.button = button;
41
42
        return button;
43
      },
44
45
      onClick: function () {
46
        this.f(!this.active);
47
      }
48
    });
49
50
    var CoordsPickerButton = ButtonBase.extend({
51
      onAdd: function () {
52
        var button = L.DomUtil.create('button', 'ion-pin shadow');
53
        button.setAttribute('data-tooltip', _.t('button.location'));
54
55
        // Click propagation isn't disabled as this causes problems with the
56
        // location picking mode; instead propagation is stopped in onClick().
57
        L.DomEvent.addListener(button, 'click', this.onClick, this);
58
59
        this.button = button;
60
61
        return button;
62
      },
63
64
      onClick: function (e) {
65
        L.DomEvent.stopPropagation(e);
66
        this.f(!this.active);
67
      }
68
    });
69
70
    function mkMarker(dict, iconFunc, router) {
71
      return function (d) {
72
        var m = L.circleMarker([d.nodeinfo.location.latitude, d.nodeinfo.location.longitude], iconFunc(d));
73
74
        m.resetStyle = function resetStyle() {
75
          m.setStyle(iconFunc(d));
76
        };
77
78
        m.on('click', router.node(d));
79
        m.bindTooltip(d.nodeinfo.hostname);
80
81
        dict[d.nodeinfo.node_id] = m;
82
83
        return m;
84
      };
85
    }
86
87
    function addLinksToMap(dict, linkScale, graph, router) {
88
      graph = graph.filter(function (d) {
89
        return 'distance' in d && d.type !== 'VPN';
90
      });
91
92
      return graph.map(function (d) {
93
        var opts = {
94
          color: d.type === 'Kabel' ? '#50B0F0' : linkScale(1 / d.tq),
95
          weight: 4,
96
          opacity: 0.5,
97
          dashArray: 'none'
98
        };
99
100
        var line = L.polyline(d.latlngs, opts);
101
102
        line.resetStyle = function resetStyle() {
103
          line.setStyle(opts);
104
        };
105
106
        line.bindTooltip(d.source.node.nodeinfo.hostname + ' – ' + d.target.node.nodeinfo.hostname + '<br><strong>' + helper.showDistance(d) + ' / ' + helper.showTq(d) + '</strong>');
107
        line.on('click', router.link(d));
108
109
        dict[d.id] = line;
110
111
        return line;
112
      });
113
    }
114
115
    var iconOnline = {
116
      color: '#1566A9',
117
      fillColor: '#1566A9',
118
      radius: 6,
119
      fillOpacity: 0.5,
120
      opacity: 0.5,
121
      weight: 2,
122
      className: 'stroke-first'
123
    };
124
    var iconOffline = {
125
      color: '#D43E2A',
126
      fillColor: '#D43E2A',
127
      radius: 3,
128
      fillOpacity: 0.5,
129
      opacity: 0.5,
130
      weight: 1,
131
      className: 'stroke-first'
132
    };
133
    var iconLost = {
134
      color: '#D43E2A',
135
      fillColor: '#D43E2A',
136
      radius: 4,
137
      fillOpacity: 0.8,
138
      opacity: 0.8,
139
      weight: 1,
140
      className: 'stroke-first'
141
    };
142
    var iconAlert = {
143
      color: '#D43E2A',
144
      fillColor: '#D43E2A',
145
      radius: 5,
146
      fillOpacity: 0.8,
147
      opacity: 0.8,
148
      weight: 2,
149
      className: 'stroke-first'
150
    };
151
    var iconNew = { color: '#1566A9', fillColor: '#93E929', radius: 6, fillOpacity: 1.0, opacity: 0.5, weight: 2 };
152
153
    return function (config, linkScale, sidebar, router, buttons) {
154
      var self = this;
155
      var groupOnline;
156
      var groupOffline;
157
      var groupNew;
158
      var groupLost;
159
      var groupLines;
160
      var savedView;
161
162
      var map;
163
      var userLocation;
164
      var layerControl;
165
      var baseLayers = {};
166
167
      var locateUserButton = new LocateButton(function (d) {
168
        if (d) {
169
          enableTracking();
170
        } else {
171
          disableTracking();
172
        }
173
      });
174
175
      var mybuttons = [];
176
177
      function addButton(button) {
178
        var el = button.onAdd();
179
        mybuttons.push(el);
180
        buttons.appendChild(el);
181
      }
182
183
      function clearButtons() {
184
        mybuttons.forEach(function (d) {
185
          buttons.removeChild(d);
186
        });
187
      }
188
189
      var showCoordsPickerButton = new CoordsPickerButton(function (d) {
190
        if (d) {
191
          enableCoords();
192
        } else {
193
          disableCoords();
194
        }
195
      });
196
197
      function saveView() {
198
        savedView = {
199
          center: map.getCenter(),
200
          zoom: map.getZoom()
201
        };
202
      }
203
204
      function enableTracking() {
205
        map.locate({
206
          watch: true,
207
          enableHighAccuracy: true,
208
          setView: true
209
        });
210
        locateUserButton.set(true);
211
      }
212
213
      function disableTracking() {
214
        map.stopLocate();
215
        locationError();
216
        locateUserButton.set(false);
217
      }
218
219
      function enableCoords() {
220
        map.getContainer().classList.add('pick-coordinates');
221
        map.on('click', showCoordinates);
222
        showCoordsPickerButton.set(true);
223
      }
224
225
      function disableCoords() {
226
        map.getContainer().classList.remove('pick-coordinates');
227
        map.off('click', showCoordinates);
228
        showCoordsPickerButton.set(false);
229
      }
230
231
      function showCoordinates(e) {
232
        router.gotoLocation(e.latlng);
233
        disableCoords();
234
      }
235
236
      function locationFound(e) {
237
        if (!userLocation) {
238
          userLocation = new LocationMarker(e.latlng).addTo(map);
239
        }
240
241
        userLocation.setLatLng(e.latlng);
242
        userLocation.setAccuracy(e.accuracy);
243
      }
244
245
      function locationError() {
246
        if (userLocation) {
247
          map.removeLayer(userLocation);
248
          userLocation = null;
249
        }
250
      }
251
252
      function contextMenuOpenLayerMenu() {
253
        document.querySelector('.leaflet-control-layers').classList.add('leaflet-control-layers-expanded');
254
      }
255
256
      var el = document.createElement('div');
257
      el.classList.add('map');
258
259
      map = L.map(el, options);
260
      var now = new Date();
261
      config.mapLayers.forEach(function (item, i) {
262
        if ((typeof item.config.start === 'number' && item.config.start <= now.getHours()) || (typeof item.config.end === 'number' && item.config.end > now.getHours())) {
263
          item.config.order = item.config.start * -1;
264
        } else {
265
          item.config.order = i;
266
        }
267
      });
268
269
      config.mapLayers = config.mapLayers.sort(function (a, b) {
270
        return a.config.order - b.config.order;
271
      });
272
273
      var layers = config.mapLayers.map(function (d) {
274
        return {
275
          'name': d.name,
276
          'layer': 'url' in d ? L.tileLayer(d.url.replace('{retina}', L.Browser.retina ? '@2x' : ''), d.config) : console.warn('Missing map url')
277
        };
278
      });
279
280
      map.addLayer(layers[0].layer);
281
282
      layers.forEach(function (d) {
283
        baseLayers[d.name] = d.layer;
284
      });
285
286
      map.on('locationfound', locationFound);
287
      map.on('locationerror', locationError);
288
      map.on('dragend', saveView);
289
      map.on('contextmenu', contextMenuOpenLayerMenu);
290
291
      addButton(locateUserButton);
292
      addButton(showCoordsPickerButton);
293
294
      layerControl = L.control.layers(baseLayers, [], { position: 'bottomright' });
295
      layerControl.addTo(map);
296
297
      map.zoomControl.setPosition('topright');
298
299
      var clientLayer = new ClientLayer({ minZoom: config.clientZoom });
300
      clientLayer.addTo(map);
301
      clientLayer.setZIndex(5);
302
303
      var labelLayer = new LabelLayer({ minZoom: config.labelZoom });
304
      labelLayer.addTo(map);
305
      labelLayer.setZIndex(6);
306
307
      map.on('zoom', function () {
308
        clientLayer.redraw();
309
        labelLayer.redraw();
310
        console.log(map.getZoom());
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
311
      });
312
313
      map.on('baselayerchange', function (e) {
314
        map.options.maxZoom = e.layer.options.maxZoom;
315
        clientLayer.options.maxZoom = map.options.maxZoom;
316
        labelLayer.options.maxZoom = map.options.maxZoom;
317
        if (map.getZoom() > map.options.maxZoom) {
318
          map.setZoom(map.options.maxZoom);
319
        }
320
321
        var style = document.querySelector('.css-mode:not([media="not"])');
322
        if (style && e.layer.options.mode !== '' && !style.classList.contains(e.layer.options.mode)) {
323
          style.media = 'not';
324
          labelLayer.updateLayer();
325
        }
326
        if (e.layer.options.mode) {
327
          var newStyle = document.querySelector('.css-mode.' + e.layer.options.mode);
328
          newStyle.media = '';
329
          newStyle.appendChild(document.createTextNode(''));
330
          labellayer.updateLayer();
331
        }
332
      });
333
334
      var nodeDict = {};
335
      var linkDict = {};
336
      var highlight;
337
338
      function resetMarkerStyles(nodes, links) {
339
        Object.keys(nodes).forEach(function (d) {
340
          nodes[d].resetStyle();
341
        });
342
343
        Object.keys(links).forEach(function (d) {
344
          links[d].resetStyle();
345
        });
346
      }
347
348
      function setView(bounds) {
349
        map.fitBounds(bounds, { paddingTopLeft: [sidebar(), 0], maxZoom: config.nodeZoom });
350
      }
351
352
      function goto(m) {
353
        var bounds;
354
355
        if ('getBounds' in m) {
356
          bounds = m.getBounds();
357
        } else {
358
          bounds = L.latLngBounds([m.getLatLng()]);
359
        }
360
361
        setView(bounds);
362
363
        return m;
364
      }
365
366
      function updateView(nopanzoom) {
367
        resetMarkerStyles(nodeDict, linkDict);
368
        var m;
369
370
        if (highlight !== undefined) {
371
          if (highlight.type === 'node' && nodeDict[highlight.o.nodeinfo.node_id]) {
372
            m = nodeDict[highlight.o.nodeinfo.node_id];
373
            m.setStyle({ color: 'orange', weight: 20, fillOpacity: 1, opacity: 0.7, className: 'stroke-first' });
374
          } else if (highlight.type === 'link' && linkDict[highlight.o.id]) {
375
            m = linkDict[highlight.o.id];
376
            m.setStyle({ weight: 4, opacity: 1, dashArray: '5, 10' });
377
          }
378
        }
379
380
        if (!nopanzoom) {
381
          if (m) {
382
            goto(m);
383
          } else if (savedView) {
384
            map.setView(savedView.center, savedView.zoom);
385
          } else {
386
            setView(config.fixedCenter);
387
          }
388
        }
389
      }
390
391
      function mapRTree(d) {
392
        return {
393
          minX: d.nodeinfo.location.latitude, minY: d.nodeinfo.location.longitude,
394
          maxX: d.nodeinfo.location.latitude, maxY: d.nodeinfo.location.longitude,
395
          node: d
396
        };
397
      }
398
399
      self.setData = function setData(data) {
400
        nodeDict = {};
401
        linkDict = {};
402
403
        if (groupOffline) {
404
          groupOffline.clearLayers();
405
        }
406
407
        if (groupOnline) {
408
          groupOnline.clearLayers();
409
        }
410
411
        if (groupNew) {
412
          groupNew.clearLayers();
413
        }
414
415
        if (groupLost) {
416
          groupLost.clearLayers();
417
        }
418
419
        if (groupLines) {
420
          groupLines.clearLayers();
421
        }
422
423
        var lines = addLinksToMap(linkDict, linkScale, data.graph.links, router);
424
        groupLines = L.featureGroup(lines).addTo(map);
425
426
        var nodesOnline = helper.subtract(data.nodes.all.filter(helper.online), data.nodes.new);
427
        var nodesOffline = helper.subtract(data.nodes.all.filter(helper.offline), data.nodes.lost);
428
429
        var markersOnline = nodesOnline.filter(helper.hasLocation)
430
          .map(mkMarker(nodeDict, function () {
431
            return iconOnline;
432
          }, router));
433
434
        var markersOffline = nodesOffline.filter(helper.hasLocation)
435
          .map(mkMarker(nodeDict, function () {
436
            return iconOffline;
437
          }, router));
438
439
        var markersNew = data.nodes.new.filter(helper.hasLocation)
440
          .map(mkMarker(nodeDict, function () {
441
            return iconNew;
442
          }, router));
443
444
        var markersLost = data.nodes.lost.filter(helper.hasLocation)
445
          .map(mkMarker(nodeDict, function (d) {
446
            if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAgeAlert, 'days'))) {
447
              return iconAlert;
448
            }
449
450
            if (d.lastseen.isAfter(moment(data.now).subtract(config.maxAge, 'days'))) {
451
              return iconLost;
452
            }
453
            return null;
454
          }, router));
455
456
        groupOffline = L.featureGroup(markersOffline).addTo(map);
457
        groupLost = L.featureGroup(markersLost).addTo(map);
458
        groupOnline = L.featureGroup(markersOnline).addTo(map);
459
        groupNew = L.featureGroup(markersNew).addTo(map);
460
461
        var rtreeOnlineAll = rbush(9);
462
463
        rtreeOnlineAll.load(data.nodes.all.filter(helper.online).filter(helper.hasLocation).map(mapRTree));
464
465
        clientLayer.setData(rtreeOnlineAll);
466
        labelLayer.setData({
467
          online: nodesOnline.filter(helper.hasLocation),
468
          offline: nodesOffline.filter(helper.hasLocation),
469
          new: data.nodes.new.filter(helper.hasLocation),
470
          lost: data.nodes.lost.filter(helper.hasLocation)
471
        });
472
473
        updateView(true);
474
      };
475
476
      self.resetView = function resetView() {
477
        disableTracking();
478
        highlight = undefined;
479
        updateView();
480
      };
481
482
      self.gotoNode = function gotoNode(d, update) {
483
        disableTracking();
484
        highlight = { type: 'node', o: d };
485
        updateView(update);
486
      };
487
488
      self.gotoLink = function gotoLink(d, update) {
489
        disableTracking();
490
        highlight = { type: 'link', o: d };
491
        updateView(update);
492
      };
493
494
      self.gotoLocation = function gotoLocation() {
495
        // ignore
496
      };
497
498
      self.destroy = function destroy() {
499
        clearButtons();
500
        map.remove();
501
502
        if (el.parentNode) {
503
          el.parentNode.removeChild(el);
504
        }
505
      };
506
507
      self.render = function render(d) {
508
        d.appendChild(el);
509
        map.invalidateSize();
510
      };
511
512
      return self;
513
    };
514
  });
515